﻿//#define _CRT_SECURE_NO_WARNINGS
//#include <stdio.h>
#include "common.h"

/*
数组名：
	数组名通常都是表示数组首元素的地址，数组名的类型就是指针类型。
	注意：1. sizeof(数组名) 得到的是数组的大小，而不是指针的大小。单位是字节。
	2. &数组名 得到的是也是整个数组的地址。

指针是用来存放地址的变量，它可以指向任意数据类型，
	包括整型、浮点型、字符型、结构体、数组等。
	地址是唯一标识一块内存空间的标识符，它是一个整数值。
	指针的大小在 32 位系统上为 4 字节(32个0或1)，在 64 位系统上为 8 字节。
	特殊指针 *void 指针：void* 指针可以指向任意数据类型，但是不能直接解引用；
		在使用 *void 指针时，需要强制类型转换为其他指针类型。

指针类型的意义：
	一个 16 进制的数在内存中需要 4 个二进制的位来表示，0xf = 0b1111
	所以两位 16 进制的数就是一个字节，那么一个普通整型能存 8 位 16 进制数，
	比如： 0x 12345678 = 0b 0001 0010 0011 0100 0101 0110 0111 1000
	在内存中，地址是 16 进制的数，那么一字节可以存放 2 位 16 进制数，
	指针类型决定了指针在被解引用时访问值的几个字节，
	比如 int* p; 则 *p 指针可以访问整型变量，即能够改变 p 地址所指向的，
	连续 4 个字节（8 个 16 进制数）中的值。
	指针类型决定了指针的运算能力，比如指针可以加减，可以进行比较运算等。
	不同类型的指针在进行运算时，计算改变的内存空间大小不同。
	也就是说操作不同类型的指针访问内存时，操作的步长不同；
	此外，不同类型的指针解引用赋值时，改变原地址的值的类型不同，
	这个类型由被解引用的指针类型决定。

野指针的危害：
	野指针是指指针变量指向的地址不存在，或者已经被释放掉，
	这样的指针变量在使用时，会导致程序崩溃，或者产生未定义的行为。
	野指针的产生有很多种原因，比如：
	1. 指针变量没有初始化，此时的指针变量指向的地址是随机值 0xcccccccd 对应 烫。
	2.指针越界访问时，比如指针变量指向的地址超出了数组的范围。
	3.函数返回指针指向函数的局部变量时，局部变量在函数退出后会被销毁
		这里注意函数的局部变量被销毁后，其中存放的值可能会被保留（地址没有被使用时）
	4.空指针（如 int* p = NULL;）没有指向有效空间，解引用时，会导致程序崩溃。

指针的运算：
	指针的运算有以下几种：
	1. 指针加减整数：指针可以加减整数，指针加 1 表示往后移动指针类型含义的大小个字节，
		指针减 1 表示往前移动指针类型含义的大小个字节。运算结果依然是指针。
	2. 指针比较：指针可以进行比较运算，比较的结果是地址的大小关系。
	3. 指针赋值：指针可以赋值给其他指针，赋值后，两个指针指向同一块内存空间。
	4. 指针解引用：指针可以解引用，解引用操作会得到指针指向的内存空间的值。
	5. 指针之间的运算：指针可以与其他指针进行运算，运算的结果是地址的偏移量（zd格式整数）。
	6. 指针运算符：指针可以与指针运算符进行运算，运算的结果是指针的地址。
	7. 指针函数：指针可以作为函数参数，函数可以修改指针指向的内存空间的值。
	8. 指针数组：指针可以作为数组的元素，数组的每个元素都是一个指针。
	9. 指针结构体：指针可以作为结构体的成员，结构体的每个成员都是一个指针。
	C 语音中允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较，
		但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

二级指针与指针数组：
	二级指针变量是指指向指针的指针，也就是说，它指向一个指针变量，存放一级指针的地址。
	因为内存是连续的，所以对于一个非数组变量，
	二级指针的地址总是比它指向的一级指针的地址高。
	指针数组是指数组的元素都是指针，二级指针数组是指数组的每个元素都是一个二级指针 ...
		指针数组的声明形式为：
			类型名* 指针变量名[数组长度] = { 指针1, 指针2,... };
		指针数组的简便使用形式为：（ [] 运算符的优先级大于 * 运算符）
			*指针变量名[下标]，即先得到数组本身，然后解引用访问元素。
		存放数组地址的情况简便使用形式为：
			指针变量名[下标][下标]，即先得到数组本身，然后通过下标访问数组元素。

二级指针的应用：
	1. 二维数组：二维数组的每个元素都是一个指针，可以用来实现二维数组。
	2. 二叉树：二叉树的每个节点都有一个指针指向它的左右子节点，可以用来实现二叉树。
	3. 链表：链表的每个数据项都有一个指针指向下一个数据项，可以用来实现链表。
	4. 动态内存分配：动态内存分配是指程序运行时，根据需要分配内存，
		释放内存，而二级指针可以用来实现动态内存分配。
	5. 线程间通信：线程间通信是指多个线程之间需要通信，
		而二级指针可以用来实现线程间通信。
	6. 指针运算：指针运算是指指针与指针之间的运算，可以用来实现指针运算。
	7. 指针函数：指针函数是指指针作为函数参数，可以用来实现指针函数。
	8. 指针数组：指针数组是指指针的数组，每个指针都指向一个数组元素，
		可以用来实现指针数组。
	9. 指针结构体：指针结构体是指指针作为结构体的成员，可以用来实现指针结构体。

数组指针：
	数组指针是指指向整个数组地址（不是首元素地址）的指针，数组指针可以用来访问数组的元素。
	数组指针的声明形式为：类型名 (*指针变量名)[数组长度] = 数组的地址;
	简便使用形式为：(*指针变量名)[下标]，即先得到数组本身，然后在通过下标访问数组元素
	数组指针如果指向指针数组的地址，则可以用来访问指针数组的元素。用法如下：
		声明：类型名 (*指针变量名)[数组长度] = 指针数组的地址;
		这里数组长度为指针数组的长度（大于等于指针数组的长度）
		简便使用：(*指针变量名)[下标][下标]，先解引用得到指针数组本身，
			再通过下标访问指针数组元素，再通过下标访问指针数组元素的元素。

数组指针数组：
	数组指针数组是指数组的元素都是数组指针的数组，数组指针数组也可以用来访问数组的元素。
	数组指针数组的声明形式为：
		类型名 (*指针变量名[数组长度])[数组长度] = { {数组指针1}, {数组指针2},... };
		注意这里的第一个数组长度 () 内的 是指要声明的数组指针数组的长度，
		第二个数组长度 () 后的 是指每个数组指针指向的数组的长度。
	简便使用形式为：
		(*指针变量名[下标])[下标]，即先得到下标对应的数组指针元素，
		再通过下标访问数组指针数组的数组指针元素，再解引用得到其指向的数组，
		然后通过下标访问其指向的数组的元素。如果数组指针指向的数组是指针数组，
		则需要进一步使用下标访问操作才能得到指针数组里的数组的内部元素。
		这里需要注意 () 运算符的优先级大于 [] 运算符

函数指针：
	函数指针是指指向函数的指针，可以用来实现函数的动态调用（函数式编程）
	函数指针的声明形式为：
		函数返回类型 (*指针变量名)(参数表列1类型，参数表列2类型，...) = &函数名; & 可不写

函数指针数组：
	函数指针数组是指数组的元素都是函数指针的数组，可以用来实现函数的动态调用。
	其声明与调用参见代码示例。指向函数指针数组的指针、函数指针数组的指针数组也参见示例代码

回调函数：
	回调函数是指函数指针作为参数，在函数内部调用函数，被调用的函数通过指针调用。
	示例参见计算器代码部分
*/

int add_self(int a, int b) // 函数类似于全局变量，即就算没使用也有地址
{
	return a + b;
}

int sub_self(int a, int b)
{
	return a - b;
}

int mul_self(int a, int b)
{
	return a * b;
}

int div_self(int a, int b)
{
	return a / b;
}  // 在 <stdlib.h> 文件中有 div 函数，可以直接使用

//一些位操作
int bit_and(int a, int b)
{
	return a & b;
}

int bit_or(int a, int b)
{
	return a | b;
}

int bit_xor(int a, int b)
{
	return a ^ b;
}

int bit_left_num(int a, int b)
{
	return a << b;
}

int main()
{
	// 指针类型的意义
	{
		print_start_line("指针类型的意义部分开始");
		int a = 10000;
		/* 调试发现：整数10000存在 a 的地址为中的前 4 位 16 进制数，
			a 地址对应的完整值为 10 27 00 00 这里注意VS内存中地址
			储存的值是反方向的，所以 10 27 00 00 实际应该是 00 00 27 10
			对应 10000 这个整数 */

		char* pc = (char*)&a; //声明一个字符型指针变量 p 
		*pc = 5; // 将 a 的地址赋值给 p，并将 p 指向的地址往后 1 个字节处的值改为 5
		/* 注意上面的解引用操作赋值只改变了 1 个字节的值，也就是说 a 的值
			被改为了 05 27 00 00 反过来看，就是 00 00 27 05 对应 9989 这个整数 */
		printf("a = %d\n", a); // 输出 a 的值，输出结果为 9989
		int* pa = &a; //声明一个整型指针变量 p
		if (pa == pc) // 判断 p 与 a 的地址是否相同
			printf("pa == %p, pc == %p\n", pa, pc); // 输出
		if (pa + 1 == pc + 1) // 不同类型的指针加减 1 跳过不同大小的内存空间
			printf("pa + 1 == %p, pc + 1 == %p\n", pa + 1, pc + 1); // 不输出
		else // 一个 int* + 1 跳过 4 个字节，一个 char* + 1 跳过 1 个字节
			printf("pa 与 pc 的地址不同，pa + 1 == %p, pc + 1 == %p\n",
				pa + 1, pc + 1); // 输出 pa + 1 与 pc + 1 的地址，差 3 个字节

		float* pf = (float*)&a; //声明一个浮点型指针变量 p
		*pa = 100; // 地址 a 的值改为 64 00 00 00 对应整数 100
		*pf = 100; // 地址 a 的值改为 00 00 c8 42 对应浮点数 100
		// 经过 *pf = 100; 操作后 a 存放的是浮点数 100
		int a_copy = 100;
		if (a_copy == a) // 地址 a 的值改为 64 00 00 00 对应整数 100
			printf("a_copy == a，a_copy = %d, a = %d\n", a_copy, a);
		else
			printf("a_copy != a，a_copy = %d, a = %d\n", a_copy, a);// 输出

		// void* 指针
		void* pv = &a; // 声明一个 void* 指针变量 p
		a = 100;
		printf("pv = %p, *pv = %d\n", pv, *(int*)pv); // 输出 pv 指向的地址的值
		int* pi = (int*)pv; // 强制类型转换为 int* 指针
		*pi = 200; // 地址 a 的值改为整数 200
		printf("pv = %p, pi = %p, *pi = %d\n", pv, pi, *pi); // 输出 pv pi *pi 的值

		print_end_line("指针类型的意义部分结束");
	}

	// 指针的运算
	{
		print_start_line("指针的运算部分开始");
#define SIZE 10
		int arr[SIZE];
		int* pa1 = NULL; // 空指针
		for (pa1 = &arr[0]; pa1 < &arr[SIZE];) // 建议写为 pa1 <= &arr[SIZE-1];
			*pa1++ = 0; // 初始化数组
		/* 这里等同于 *pa1 = 0; ++pa1; 循环执行 SIZE 次，对指针地址自加
		如果写为 (*pa1)++ = 0 则表示 *pa1 = 0；++(*pa1); 对指针解引用的值自加 */
		printf("数组 arr 内容：\n");
		int i;
		for (i = 0; i < SIZE; i++)
			printf("%d ", arr[i]); // 输出数组内容
		printf("arr[SIZE - 1] - arr[0] = %zd\n", &arr[SIZE - 1] - &arr[0]);
		//指针直接的运算得到的值为指针之间元素的个数，
		// 这里是 9 个(arr 首位地址之间为 9 个元素)
		//要注意指向同一块内存空间的指针之间的运算才有意义

		printf("测试字符串长度为：%d\n",
			my_strlen("我的名字叫做小真，i'm just a student."));

		print_end_line("指针的运算部分结束");
	}

	// 数组名
	{
		print_start_line("数组名部分开始");
		int arr_name[10] = { 0 };
		printf("arr_name 的首元素地址为：%p\n", arr_name); // arr_name 类型：int [10]
		printf("arr_name 的大小为：%zd\n", sizeof(arr_name));
		printf("arr_name 的首元素的地址为：%p\n",
			&arr_name[0]);// &arr_name[0] 类型：int*
		printf("arr_name 的地址为：%p\n", &arr_name); // &arr_name 类型：int (*)[10]

		printf("arr_name + 1 = %p\n", arr_name + 1);  // +4
		printf("&arr_name[0] + 1 = %p\n", &arr_name[0] + 1); // +4
		printf("&arr_name + 1 = %p\n", &arr_name + 1); // +40

		print_end_line("数组名部分结束");
	}

	// 二级指针与指针数组
	{
		print_start_line("二级指针与指针数组部分开始");
		char a = 'a';
		char* pc1 = &a;
		char** ppc1 = &pc1; // 二级指针
		printf("二级指针的大小为：%zd\n", sizeof(char**));
		printf("二级指针 ppc1 的地址为：%p\n", ppc1);
		// 输出二级指针指向的地址
		printf("一级指针 pc1 的地址为：%p\n", pc1);
		printf("二级指针 ppc1 两次解引用的值为：%c\n", **ppc1);
		// 输出二级指针指向的地址指向的地址
		printf("一级指针 pc1 的地址加一为：%p\n", pc1 + 1);
		printf("二级指针 ppc1 一次解引用的地址加一为：%p\n", *ppc1 + 1);
		printf("二级指针 ppc1 的地址加一的值为：%p\n", ppc1 + 1);
		printf("ppc1 - pc1 = %lld\n", ppc1 - pc1); // 指针大小之差
		printf("以上是 char** 基本了解-------------------------\n");
		int b = 100;
		int* pb1 = &b;
		int** ppb1 = &pb1; // 二级指针
		printf("二级指针 ppb1 的地址为：%p\n", ppb1);
		// 输出二级指针指向的地址
		printf("一级指针 pb1 的地址为：%p\n", pb1);
		printf("二级指针 ppb1 两次解引用的值为：%d\n", **ppb1);
		// 输出二级指针指向的地址指向的地址
		printf("一级指针 pb1 的地址加一为：%p\n", pb1 + 1);
		printf("二级指针 ppb1 一次解引用的地址加一为：%p\n", *ppb1 + 1);
		printf("二级指针 ppb1 的地址加一的值为：%p\n", ppb1 + 1);
		printf("ppb1 - pb1 = %lld\n", ppb1 - pb1);
		printf("以上是 int** 基本了解------------------------------\n");

		int arr_int[3][4] = {
			{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
		int* p_arr1 = arr_int; // 指向数组的二级指针，数组本质上是一个指针（报警告）
		printf("二级指针 p_arr1 的地址为：%p\n", p_arr1);
		// 数组首元素的首元素地址
		printf("arr_int 的地址为：%p\n", arr_int); // 数组的首元素地址
		printf("&arr_int 的地址为：%p\n", &arr_int); // 数组的地址
		printf("p_arr1 + 1 的地址为：%p\n", p_arr1 + 1);// 跳过一个 int
		printf("arr_int + 1 的地址为：%p\n", arr_int + 1);// 跳过一个一维数组
		printf("&arr_int + 1 的地址为：%p\n", &arr_int + 1);// 跳过整个数组
		printf("p_arr1[0][1] 的值为：%d\n", *(p_arr1 + 1));

		printf("以上指针数组均是错误示范，正确的指针数组使用示范如下：\n");
		// 指针数组
		int arr_test[5] = { 1, 2, 3, 4, 5 }; // 普通数组
		int* p_arr_test[5] = { // 指针数组，每个元素是一个指针
			arr_test, arr_test + 1, arr_test + 2, arr_test + 3, arr_test + 4 };
		int i_p;
		for (i_p = 0; i_p < 5; ++i_p)
			printf("*p_arr_test[%d] = %d\n", i_p, *p_arr_test[i_p]);

		// 可以先将数组的每个元素的地址作为二级数组的元素
		int size1 = sizeof(arr_int) / sizeof(arr_int[0]);
		int size2 = sizeof(arr_int[0]) / sizeof(arr_int[0][0]);
		int* p_arr2[3]; // p_arr2 是一个指针数组，每个元素是一个指针
		int i_pp;
		for (i_pp = 0; i_pp < size1; ++i_pp)
			// 数组的每个元素的地址作为二级数组的元素
			p_arr2[i_pp] = arr_int[i_pp];
		int j_pp;
		for (i_pp = 0; i_pp < size1; ++i_pp)
		{	// 输出二级数组的所有元素
			for (j_pp = 0; j_pp < size2; ++j_pp)
				printf("%d ", p_arr2[i_pp][j_pp]);
			printf("\n");
		}

		// 指针数组的函数的形式参数写法
		int arr_int_1[5] = { 1, 2, 3, 4, 5 };
		int arr_int_2[5] = { 6, 7, 8, 9, 10 };
		int arr_int_3[5] = { 11, 12, 13, 14, 15 };
		int* p_arr_int[3] = { arr_int_1, arr_int_2, arr_int_3 };
		print_array_pointer(p_arr_int, 3, 5); // 调用函数打印指针数组

		print_end_line("二级指针与指针数组部分结束");
	}

	// 数组指针
	{
		print_start_line("数组指针部分开始");
		char* p1[10]; // 指针数组：每个元素是一个指针
		char (*p2)[10]; // 数组指针：指向数组的指针，这里的数组大小为 10 个 char

		char arr_test[10] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j' };
		char (*p3)[10] = &arr_test; // 指向数组的指针，注意数组的类型为 char 即字符数组
		//注意这里的数组元素 10 不能省略，否则会报错
		int i;
		for (i = 0; i < 10; ++i)
			printf("arr_test[%d] = %c\n",
				i, (*p3)[i]);// 输出指针数组的每个元素的值，同 *(*(p3) + i)
		// 这里的 *p3 得到 arr_test 再加上 i 得到 arr_test 的第 i+1 个元素地址
		// 再用 * 解引用得到该元素的值，所以不建议用这种方式

		char arr1[4] = { 'a', 'b', 'c', 'd' };
		char arr2[4] = { '1', '2', '3', '4' };
		char arr3[4] = { 'A', 'B', 'C', 'D' };

		char* p4[3] = { arr1, arr2, arr3 }; // 指针数组
		char* (*p5)[3] = &p4; // 数组指针，指向指针数组的指针，这里类型 char (*)[3]
		int j;
		for (i = 0; i < 3; ++i)
		{
			for (j = 0; j < 4; ++j)
				printf("p4[%d][%d] = %c  ",
					i, j, (*p5)[i][j]); // *p5 == p4，同 *(p5 + i)
			// 同 (*(*p5 + i))[j] 或 *((*(*p5 + i)) + j) 都非常别扭
			printf("\n");
		}

		// 二维数组的打印，使用数组指针传参
		int arr_2d[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
		print_2d_array(arr_2d, 3, 4); // 调用函数打印二维数组

		// 数组指针数组
		int arr_i1[5] = { 1, 2, 3, 4, 5 };
		int arr_i2[5] = { 10, 20, 30, 40, 50 };
		int arr_i3[5] = { 100, 200, 300, 400, 500 };
		int (*p_arr_i[3])[5] = { &arr_i1, &arr_i2, &arr_i3 };
		// 数组指针数组，存放数组指针的数组，所以 p_arr_i本质上是一个数组
		/* 更复杂的情况为 arr_i1、arr_i2、arr_i3 为指针数组，
			即相当于把 arr_i1、arr_i2、arr_i3 作为元素存入一个数组中，
			然后由多个这样的数组组成一个数组指针数组（不做测试，用到在说）*/
		for (i = 0; i < 3; ++i)
		{
			for (j = 0; j < 5; ++j)
				printf("p_arr_i[%d][%d] = %3d  ", i, j, (*p_arr_i[i])[j]);
			// 同 *(**(p_arr_i + i) + j)
			// 或 *(*p_arr_i[i] + j) 或 (**(p_arr_i + i))[j] 都非常别扭
			printf("\n");
		}

		print_end_line("数组指针部分结束");
	}

	// 指针指向字符串的情况
	{
		print_start_line("指针指向字符串的情况部分开始");
		//这里的字符串是常量，字符串指针指向的地址是常量，常量不能修改（没有 const 也不能修改）
		const char* pc1 = "i am a abnormal."; // const 放左边，表示字符串内容不可修改
		// 这种写法是将字符串首元素地址赋值给字符串指针
	// *pc1 = 'I'; // 字符串指针指向的地址不能修改，所以这里会报错
		printf("字符串指针 pc1 的值为：%s\n", pc1);

		// 指针与数组同字符串比较
		test_pointer();

		print_end_line("指针指向字符串的情况部分结束");
	}

	// 函数指针
	{
		print_start_line("函数指针部分开始");
		printf("&add_self = %p\n", &add_self);
		printf("add = %p\n", add_self); // 对于函数 &函数名 和 函数名 本质上是一样的

		int (*p_add)(int n, int m) = &add_self;
		// &add == add 函数指针，指向 add 函数，这里的 n、m 可以不写
		int retn = (*p_add)(10, 20); // * 可以不写，调用函数指针传参，同 add(10, 20)
		printf("add(10, 20) = %d\n", retn);

		//(*(void(*)())0)();  // (void(*)()) 是类型，将 0 强制类型转换为函数指针类型，
			// 然后解引用（可不写*）在调用，调用的是 0 地址处的函数

		//void (*signal(int, void(*)(int)))(int);
			// signal 函数的原型，有两个参数，第一个参数是 int 类型，
			// 第二个参数是函数指针 void(*)(int) 类型（该函数指针指向的函数参数是 int 类型，
			// 返回值是 void 类型）。
			// signal 函数返回值是也是函数指针 void(*)(int) 类型（该函数指针
			// 指向的函数参数是 int 类型，返回值是 void 类型）。
		/*简化上述代码:
		typedef void(*sig_f)(int);    // 信号处理函数类型定义，注意名称位置
		sig_f signal(int, sig_f) */

		// 用函数式编程实现一个简单两个数的计算器
		//calculate();

		// 函数指针数组
		int (*arr_func1[4])(int, int) = {
			add_self, sub_self, mul_self, div_self };//函数指针数组
		// 使用函数指针数组给上面的计算器添加位操作功能（见 study01 文件代码）
		int (*arr_func2[4])(int, int) = {
			bit_and, bit_or, bit_xor, bit_left_num };

		int i;
		for (i = 0; i < 4; ++i)
			printf("arr_func[%d](20, 3) = %d\n", i, arr_func1[i](20, 3));
		printf("\n");

		// 指向【函数指针数组】的指针
		int (*(*p_arr_func)[4])(int, int) = &arr_func1;
		//p_arr_func 存 arr_func1 地址
		for (i = 0; i < 4; ++i)
			printf("(*p_arr_func)[%d](10, 3) = %d\n",
				i, (*p_arr_func)[i](10, 3));// 这里的 (*) 不能省，因为是指针
		printf("\n");

		//【函数指针数组】的指针数组
		int (*(*arr_p_arr_func[2])[4])(int, int) = { &arr_func1, &arr_func2 };
		int j;
		for (i = 0; i < 2; ++i)
			for (j = 0; j < 4; ++j)
				printf("(*arr_p_arr_func[%d])[%d](50, 2) = %d\n",
					i, j, (*arr_p_arr_func[i])[j](50, 2));
		// 无限指针、无限数组...到此为止，不在进行深入

		print_end_line("函数指针部分结束");
	}

	// 指针补充内容排序部分
	{
		print_start_line("指针补充内容排序部分开始");
		// 冒泡排序测试
		int arr_bubble1[10] = { 5, 3, 8, 6, 2, 7, 1, 9, 4, 0 };
		int sort_count1 = bubble_sort_int(arr_bubble1, 10);
		printf("冒泡交换次数：%d\n", sort_count1);
		int i;
		for (i = 0; i < 10; ++i)
			printf("%d ", arr_bubble1[i]);
		printf("\n");

		// qsort 函数学习测试
		/*void qsort(void* base, size_t num, size_t width,
			int(__cdecl* compare)(const void* elem1, const void* elem2));*/
		int arr_qsort2[10] = { 5, 8, 3, 6, 9, 2, 7, 1, 4, 0 };
		int sz = sizeof(arr_qsort2) / sizeof(arr_qsort2[0]);
		qsort(arr_qsort2, sz, sizeof(int), int_cmp_reverse);//注意 int_cmp_reverse
		for (i = 0; i < 10; ++i)
			printf("%d ", arr_qsort2[i]);
		printf("\n");
		// qsort 排序结构体
		test_struct_cmp();
		printf("\n");

		// 改造冒泡排序，使其类似于 qsort 函数
		printf("改造冒泡排序测试，使其类似于 qsort 函数\n");
		bubble_sort_more_test();

		print_end_line("指针补充内容排序部分结束");
	}

	return 0;
}
